#include "config.h"

#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <dirent.h>
#include <ctype.h>
#include <libgen.h>

#define DBG_SUBSYS S_LIBINTERFACE

#include "configure.h"
#include "adt.h"
#include "net_table.h"
#include "volume.h"
#include "storage.h"
#include "lichstor.h"
#include "lich_md.h"
#include "lichbd.h"
#include "md_map.h"
#include "dbg.h"
#include "core.h"
#include "table_proto.h"
#include "cpuset.h"
#include "volume_ctl.h"
#include "cache.h"
#include "system.h"

#include "nvmf_spec.h"

#define NVMF_ROOT     "/nvmf"
#define CORE_NS_MAX   256

uint64_t *__core_map__ = NULL;

static int core_map_init(int core_total)
{
        int ret, i, j;
        nvmf_ns_t *ns = NULL;

        ret = ymalloc((void **)&__core_map__, sizeof(uint64_t) * core_total);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        for (i = 0; i < core_total; i++) {
                ret = ymalloc((void **)&ns, sizeof(nvmf_ns_t) + sizeof(ns_info_t) * CORE_NS_MAX);
                if (unlikely(ret))
                        GOTO(err_free, ret);

                ns->count = 0;
                __core_map__[i] = (uint64_t)ns;
        }

        return 0;
err_free:
        for (j = 0; j < i; j++) {
                ns = (nvmf_ns_t *)__core_map__[j];
                yfree((void **)&ns);
        }
        yfree((void **)&__core_map__);
        __core_map__ = NULL;
err_ret:
        return ret;
}

int core_map_release(int core_total)
{
        int i;
        nvmf_ns_t *ns = NULL;

        if (__core_map__ == NULL)
                return 0;

        for (i = 0; i < core_total; i++) {
                ns = (nvmf_ns_t *)__core_map__[i];
                yfree((void **)&ns);
        }

        yfree((void **)&__core_map__);
        __core_map__ = NULL;

        return 0;
}

int core_map_add(int core_id)
{
        int ret;
        uint64_t count = 0;
        size_t size = 0;
        nvmf_ns_t *ns_new = NULL, *ns_old = NULL;

        count = ((nvmf_ns_t *)__core_map__[core_id])->count;

        size = sizeof(nvmf_ns_t) + sizeof(ns_info_t) * CORE_NS_MAX * (count / CORE_NS_MAX + 1);
        ret = ymalloc((void **)&ns_new, size);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        size = sizeof(nvmf_ns_t) + sizeof(ns_info_t) * count;
        ns_old = (nvmf_ns_t *)__core_map__[core_id];
        memcpy(ns_new, ns_old, size);

        yfree((void **)&ns_old);
        __core_map__[core_id] = (uint64_t)ns_new;

        return 0;
err_ret:
        return ret;
}

int core_map_add_ns(ns_info_t info)
{
        int ret, core_id = 0;
        uint64_t idx = 0;

        core_id = core_hash(&(info.id));
        idx = ((nvmf_ns_t *)__core_map__[core_id])->count;
        if (idx % CORE_NS_MAX == 0 && idx > 0) {
                ret = core_map_add(core_id);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        ((nvmf_ns_t *)__core_map__[core_id])->info[idx] = info;
        ((nvmf_ns_t *)__core_map__[core_id])->count++;

        return 0;
err_ret:
        return ret;
}

nvmf_ns_t *core_map_get_ns(int core_id)
{
        if (__core_map__ == NULL)
                return NULL;

        if (((nvmf_ns_t *)__core_map__[core_id])->count)
                return (nvmf_ns_t *)__core_map__[core_id];
        else
                return NULL;
}

static int _nvmf_scan_all_ns(char *pool, const char *dir)
{
        int delen, ret, retry = 0, done = 0;
        uint64_t offset = 0, offset2 = 0;
        fileid_t fid, subfid;
        struct dirent *de0, *de;
        char uuid[MAX_NAME_LEN] = {}, path[MAX_NAME_LEN] = {};
        struct stat stbuf;
        ns_info_t nsinfo;
        mcache_entry_t *entry = NULL;

retry0:
        ret = stor_lookup1(pool, dir, &fid);
        if (unlikely(ret)) {
                if (ret == EAGAIN) {
                        USLEEP_RETRY(err_ret, ret, retry0, retry, gloconf.lease_timeout + gloconf.lease_timeout / 2, (1000 * 1000));
                } else
                        GOTO(err_ret, ret);
        }

        get_uuid(uuid);
        ret = stor_listpool_open(&fid, uuid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        while (done == 0) {
retry1:
                ret = stor_listpool(&fid, uuid, offset, (void **)&de0, &delen);
                if (unlikely(ret)) {
                        if (ret == EAGAIN) {
                                USLEEP_RETRY(err_close, ret, retry1, retry, gloconf.lease_timeout + gloconf.lease_timeout / 2, (1000 * 1000));
                        } else
                                GOTO(err_close, ret);
                }

                if (delen == 0) {
                        break;
                }

                offset2 = 0;
                dir_for_each(de0, delen, de, offset2) {
                        if (strlen(de->d_name) == 0) {
                                done = 1;
                                break;
                        } else if (delen - (int)offset2 < (int)(sizeof(*de) + MAX_NAME_LEN)) {
                                break;
                        }

                        offset += de->d_reclen;

                        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
                                continue;
                        }

                        snprintf(path, MAX_NAME_LEN, "%s/%s", dir, de->d_name);
                        ret = md_chunk_getid(pool, path, &subfid);
                        if (unlikely(ret)) {
                                GOTO(err_close, ret);
                        }

                        if (subfid.type == __VOLUME_CHUNK__) {
retry2:
                                ret = stor_getattr(pool, &subfid, &stbuf);
                                if (unlikely(ret)) {
                                        if (ret == EAGAIN) {
                                                USLEEP_RETRY(err_close, ret, retry2, retry, 10, 1000);
                                        } else
                                                GOTO(err_close, ret);
                                }

                                //volume_ctl_get(&subfid, &entry);

                                strcpy(nsinfo.pool, pool);
                                nsinfo.size = stbuf.st_size;
                                nsinfo.id = subfid;
                                nsinfo.entry = entry;

                                core_map_add_ns(nsinfo);
                        } else {
                                _nvmf_scan_all_ns(pool, path);
                        }
                }
                yfree((void **)&de0);
        }

        stor_listpool_close(&fid, uuid);

        return 0;
err_close:
        stor_listpool_close(&fid, uuid);
err_ret:
        return ret;
}

int nvmf_scan_all_ns(void *pool, void *dir)
{
        int ret;

        ret = _nvmf_scan_all_ns((char *)pool, (char *)dir);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int core_map_update()
{
        int ret;
        int core_total = cpuset_useable();

        core_map_release(core_total);

        ret = core_map_init(core_total);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = system_pool_iterator(nvmf_scan_all_ns, (void *)NVMF_ROOT);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        int count = 0, i, j;
        volid_t vol;
        uint64_t size;
        for (i = 0; i < core_total; i++) {
                count = ((nvmf_ns_t *)__core_map__[i])->count;
                for (j = 0; j < count; j++) {
                        vol = ((nvmf_ns_t *)__core_map__[i])->info[j].id;
                        size = ((nvmf_ns_t *)__core_map__[i])->info[j].size;
                        DINFO("core[%d] -- "CHKID_FORMAT", size: %lu\n", i, CHKID_ARG(&vol), size);
                }
        }

        /**
        core_map_release(core_total);
        **/

        return 0;
err_ret:
        return ret;
}
